<!-- eslint-disable prettier/prettier -->
<template>
	<div class="gojs-page h-full" :class="nodeParam.id ? 'split-panel' : 'full-panel'">
		<splitpane :splitSet="settingLR">
			<!-- #paneL 表示指定该组件为左侧面板 -->
			<template #paneL>
				<!-- 自定义左侧面板的内容 -->
				<div class="h-full" style="width: 200px" v-if="false">
					<div class="text-base font-bold data-title">{{ $t("goflow.text.input") }}</div>
					<div id="input-palette" class="palette"></div>

					<div class="text-base font-bold data-title mt-8">{{ $t("goflow.text.handle") }}</div>
					<div id="calc-palette" class="palette"></div>

					<div class="text-base font-bold data-title mt-8">{{ $t("goflow.text.output") }}</div>
					<div id="error-palette" class="palette"></div>
					<div id="output-palette" class="palette"></div>
				</div>
				<div class="h-full">
					<div class="text-sm font-bold data-title flex pb-2 items-center h-8">
						<div>{{ $t("goflow.pane_title.left") }}</div>
						<span class="font-12 text-gray-700 font-thin">{{ $t("goflow.pane_title_tips.left") }}</span>
					</div>
					<div class="diagram-panel">
						<div id="myDiagramDiv" class="diagram-canvas" :style="{ background: readonly ? '#f1f1f1' : '#fff' }"></div>

						<el-tooltip :show-after="500" content="自适应">
							<el-link :underline="false" class="diagram-toolbar cursor-pointer p-2" @click="zoomFit">
								<!-- <icon-icons8-refresh class="text-gray-600" /> -->
							</el-link>
						</el-tooltip>
						<div class="diagram-tips animate__animated animate__bounceInRight">
							<div v-if="miniTips" class="cursor-pointer flex items-center" @click="() => (miniTips = false)">
								<!-- <icon-bi-arrow-up-left-square class="pr-2" /> -->
								{{ $t("goflow.tips") }}
							</div>
							<template v-else>
								<div class="text-sm font-bold leading-6 flex justify-between" style="color: var(--el-color-primary)">
									<span>
										<i class="iconfont iconinfo" />
										{{ $t("goflow.tips.title") }}
									</span>
									<span class="cursor-pointer" @click="() => (miniTips = true)">
										<i class="iconfont iconbx-x" />
									</span>
								</div>
								<div>{{ $t("goflow.tips.tip1") }}</div>
								<div>{{ $t("goflow.tips.tip2") }}</div>
								<div>{{ $t("goflow.tips.tip3") }}</div>
								<div>{{ $t("goflow.tips.tip4") }}</div>
								<div>{{ $t("goflow.tips.tip5") }}</div>
								<div>{{ $t("goflow.tips.tip6") }}</div>
							</template>
						</div>
					</div>
				</div>
			</template>
			<!-- #paneR 表示指定该组件为右侧面板 -->
			<template #paneR v-if="nodeParam.id">
				<div class="form-info h-full">
					<div class="text-sm font-bold pl-4 flex items-center pb-2 justify-between">
						<div>{{ $t("goflow.pane_title.right") }}</div>
						<el-button
							v-if="auth.find((menu: any) => menu.path == 'mod')"
							type="primary"
							@click="() => changeNode()"
							class="ml-4"
							>{{ $t("goflow.btn.save") }}</el-button
						>
					</div>
					<el-form class="node-editer-form bg-white p-6" label-width="70px">
						<el-form-item :label="$t('goflow.form_label.name')">
							<el-input v-model="nodeParam.name" :placeholder="$t('goflow.placeholder.name')"> </el-input>
						</el-form-item>
						<el-form-item :label="$t('goflow.form_label.type')">
							<div class="flex w-full">
								<div class="flex-1">
									<el-select
										v-model="nodeParam.trigger"
										@change="
											() => (nodeParam.trigger == 'schedule' && !nodeParam.schedule ? (nodeParam.schedule = '0 0 * * * ?') : null)
										"
									>
										<el-option v-for="item in triggerType" :key="item.key" :value="item.key" :label="item.label" />
									</el-select>
								</div>
								<div class="flex-1" v-if="nodeParam.trigger == 'schedule'">
									<SansanCron v-model="nodeParam.schedule" />
								</div>
							</div>
						</el-form-item>
						<el-form-item :label="$t('goflow.form_label.rule')">
							<div class="script-editer">
								<pre>function f(sensor, context) {</pre>
								<template v-if="readonly">
									<pre style="padding-left: 45px">{{ scriptBody }}</pre>
								</template>
								<template v-else>
									<script-editer v-model:data="scriptBody" theme="chrome"></script-editer>
								</template>
								<pre style="padding-left: 45px">return sensor;</pre>
								<pre style="padding-left: 5px">}</pre>
							</div>
						</el-form-item>
						<el-form-item :label="$t('goflow.form_label.result')" v-if="false">
							<div class="script-result font-12 leading-4">
								<code>{{ scriptResult }}</code>
							</div>
						</el-form-item>
					</el-form>
				</div>
			</template>
		</splitpane>
	</div>
</template>

<script lang="ts">
import { addRuleProcessor, delRuleProcessor, modRuleProcessor } from "@/api/modules/iot/rule";
import splitpane, { ContextProps } from "@/components/SplitPane";
import SansanCron from "@/components/SansanCron/index.vue";
import go from "gojs";
import ScriptEditer from "../Frame/ScriptEditer.vue";
import { NodeInfo, ShapeParam } from "./interface";
import { Dict, getDictList } from "@/utils/cacheUtil";
import { useAuthButtons } from "@/hooks/useAuthButtons";
const $ = go.GraphObject.make;

export default {
	components: { splitpane, ScriptEditer, SansanCron },
	props: {
		ruleId: {
			type: String,
			required: true,
			default() {
				return "";
			}
		}
	},
	emits: ["reload"],
	data() {
		return {
			auth: [],
			readonly: false,
			cronValue: "3/15 * 3/15 * * ? *",
			settingLR: {
				minPercent: 20,
				defaultPercent: 50,
				split: "vertical"
			} as ContextProps,
			scriptBody: "",
			scriptPreview: "",
			scriptResult: "",
			miniTips: true,
			nodeParam: {} as NodeInfo,
			dataList: [] as NodeInfo[],
			nodeData: [],
			linkData: [],
			triggerType: [] as Dict[]
		};
	},
	computed: {},
	watch: {
		scriptBody(val) {
			let perviewBody = (val || "").replaceAll(this.exampleCode(), "");
			perviewBody = perviewBody == "\n\r" || perviewBody == "\n" ? "" : perviewBody;
			this.scriptPreview = `function f(sensor, context) {${perviewBody} \n\t return sensor;\n}`;
			this.nodeParam.script = `function f(sensor, context) {\n\t ${val || ""} \n\t return sensor;\n}`;
			try {
				this.scriptResult = eval(`${this.nodeParam.script};f({},{})`);
			} catch (e: any) {
				this.scriptResult = e;
			}
		}
	},
	mounted() {
		useAuthButtons(this.$route.query.menuId as string).then(val => {
			this.auth = val;
			this.readonly = this.auth.find((menu: any) => menu.path == "mod") ? false : true;
			this.init();
		});
	},
	methods: {
		//表单修改
		async changeNode() {
			let mySelf = this as any;
			this.nodeParam.text = this.nodeParam.name;
			mySelf.myDiagram.model.setKeyForNodeData(this.nodeParam, this.nodeParam.id);
			let { code } = await this.modNode(this.nodeParam);
			if (code == 200) {
				this.reload();
			}
		},
		//初始填充
		fillDefault(dataList: NodeInfo[]) {
			this.dataList = [];
			if (!dataList.length) {
				this.miniTips = false;
			} else {
				let mySelf = this as any;
				this.dataList = JSON.parse(JSON.stringify(dataList));
				let linkList: any[] = [];
				let nodeList: any[] = dataList.map(item => {
					item.category = "calc";
					item.text = item.name;
					item.key = item.id;

					if (item.next) {
						let link = {
							from: item.key,
							to: item.next
						};
						linkList.push(link);
					} else if (item.previous) {
						let link = {
							from: item.previous,
							to: item.key
						};
						linkList.push(link);
					}
					return item;
				});

				mySelf.myDiagram.model.nodeDataArray = nodeList;
				mySelf.myDiagram.model.linkDataArray = linkList;
				this.zoomFit();
			}
		},
		//点击一个节点，获取处理器详情
		async loadNodeInfo(node: NodeInfo) {
			this.scriptBody = "";
			if (!node.script) {
				node.script = this.initEmptyScript();
			}
			this.scriptBody =
				node.script
					.substring(node.script.indexOf("function f"))
					.replaceAll("function f(sensor, context) {", "")
					.replaceAll("\n\t return sensor;\n}", "")
					.trim() + "\n";

			this.nodeParam = node;
		},
		//缩放画布
		zoomFit() {
			let mySelf = this as any;
			mySelf.myDiagram.zoomToFit();
		},

		//初始化
		async init() {
			this.triggerType = await getDictList("rule_processor_trigger");
			class DemoForceDirectedLayout extends go.ForceDirectedLayout {
				//布局
				makeNetwork(coll: any) {
					const net = super.makeNetwork(coll);
					net.vertexes.each((vertex: any) => {
						const node = vertex.node;
						if (node !== null) vertex.isFixed = node.isSelected;
					});
					return net;
				}
			}
			let mySelf = this as any;
			mySelf.myDiagram = $(
				go.Diagram,
				"myDiagramDiv", // 为DIV.HTML元素创建一个画布
				{
					//设置画布配置
					initialContentAlignment: go.Spot.Center, // 居中显示
					"undoManager.isEnabled": true, // 支持 Ctrl-Z 和 Ctrl-Y 操作
					"toolManager.hoverDelay": 100, //tooltip提示显示延时
					"toolManager.toolTipDuration": 10000, //tooltip持续显示时间
					isReadOnly: this.readonly, //只读
					"grid.visible": false, //显示网格
					padding: 30,
					allowMove: true, //允许拖动
					allowDragOut: true,
					allowDelete: true,
					allowCopy: false,
					allowClipboard: false,
					doubleClick: null,
					initialAutoScale: go.Diagram.Uniform, // 缩放以使所有内容都适合

					"toolManager.mouseWheelBehavior": go.ToolManager.WheelZoom, //有鼠标滚轮事件放大和缩小，而不是向上和向下滚动
					"clickCreatingTool.archetypeNodeData": { category: "calc", text: "处理器", notice: "" }, // 双击新建节点(可以写入节点的默认信息)；
					layout: new DemoForceDirectedLayout()
				}
			);
			mySelf.myDiagram.linkTemplate = $(
				go.Link,
				{
					routing: go.Link.Orthogonal,
					corner: 15,
					selectable: true, //连接线是否可选
					relinkableFrom: true, //出发点是否可以改变
					relinkableTo: true //目标点是否可以改变
				},
				new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
				$(go.Shape, {
					strokeWidth: 2, //节点连接线宽度
					stroke: "#F60" //颜色
				}),
				$(go.Shape, {
					toArrow: "Standard",
					fill: "#F60", //箭头填充色
					stroke: "#F60" //外边框颜色
				}), // 箭头
				$(
					go.TextBlock,
					{
						margin: 20,
						stroke: "blue",
						font: "14px sans-serif",
						// width: 40,
						wrap: go.TextBlock.WrapDesiredSize
					},
					new go.Binding("text", "text")
				),
				{
					toolTip: $(
						go.Adornment,
						"Auto",
						$(go.Shape, {
							fill: "#FFFFCC"
						}),
						$(
							go.TextBlock,
							{
								margin: 4
							},
							new go.Binding("text", "text")
						)
					) // end of Adornment
				}
			);
			//定义输入节点
			mySelf.myDiagram.nodeTemplateMap.add(
				"input",
				$(
					go.Node,
					"auto",
					mySelf.getNodeOption(),
					new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
					//创建节点面板
					mySelf.markerNode(),
					//设置节点扩展（提供一个输出）
					mySelf.markerNodeExt(new go.Margin(0, 0, 0, 177)),
					//悬浮提示
					mySelf.markerTooltip()
				)
			);
			//定义处理节点
			mySelf.myDiagram.nodeTemplateMap.add(
				"calc",
				$(
					go.Node,
					"auto",
					mySelf.getNodeOption(),
					new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
					//创建节点面板
					mySelf.markerNode(),
					//设置节点扩展（提供一组输入与输出）
					mySelf.markerNodeExt(),
					mySelf.markerNodeExt(new go.Margin(0, 0, 0, 177)),
					//悬浮提示
					mySelf.markerTooltip(),
					{
						selectionChanged: (part: any) => {
							let node = this.dataList.find(item => item.id == part.data.id);
							//更新位置
							if (node && node.loc != part.data.loc) {
								this.modNode(part.data);
							}
						}
					}
				)
			);
			//定义错误节点
			mySelf.myDiagram.nodeTemplateMap.add(
				"error",
				$(
					go.Node,
					"auto",
					mySelf.getNodeOption(),
					new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
					//创建节点面板
					mySelf.markerNode({ color: "red" }),
					//设置节点扩展（提供一个输入，没有输出）
					mySelf.markerNodeExt(new go.Margin(3, 0, 0, 0), { height: 56 }),
					//悬浮提示
					mySelf.markerTooltip()
				)
			);
			//定义输出节点
			mySelf.myDiagram.nodeTemplateMap.add(
				"output",
				$(
					go.Node,
					"auto",
					mySelf.getNodeOption(),
					new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
					//创建节点面板
					mySelf.markerNode(),
					//设置节点扩展（提供一个输入，没有输出）
					mySelf.markerNodeExt(),
					//悬浮提示
					mySelf.markerTooltip()
				)
			);
			//填充默认数据
			// mySelf.myDiagram.model.nodeDataArray = mySelf.nodeData;
			// mySelf.myDiagram.model.linkDataArray = mySelf.linkData;
			//添加左侧面板
			// mySelf.initPalette();
			//添加事件
			mySelf.initEvent();
		},
		initEvent() {
			let mySelf = this as any;
			//点击背景
			mySelf.myDiagram.addDiagramListener("BackgroundSingleClicked", () => {
				this.nodeParam = {} as NodeInfo;
			});
			// 单击节点事件
			mySelf.myDiagram.addDiagramListener("ObjectSingleClicked", (e: any) => {
				const node = e.subject.part.data;
				this.loadNodeInfo(node);
			});

			// 修改文本
			mySelf.myDiagram.addDiagramListener("TextEdited", async (e: any) => {
				const node = e.subject.part.data;
				node.name = node.text;
				this.nodeParam.name = node.text;
				await this.modNode(this.nodeParam);
			});

			// 删除节点
			mySelf.myDiagram.addDiagramListener("SelectionDeleted", async (e: any) => {
				let info = e.subject.ff?.value?.data;

				if (this.nodeParam.id) {
					//删除关联
					this.unNodeLink({ from: this.nodeParam.previous, to: this.nodeParam.next });
					this.delNode(this.nodeParam);
				} else if (info) {
					//删除连线
					this.unNodeLink(info);
				}
			});

			// 创建节点
			mySelf.myDiagram.addDiagramListener("PartCreated", async (e: any) => {
				const node = e.subject.part.data;
				node.name = node.text;

				let pre = this.dataList.find(item => item.next == "no" || !item.next);
				if (pre) {
					node.previous = pre.id;
				}
				node.id = await this.addNode(node);
				if (pre) {
					pre.next = node.id;
					await this.modNode(pre);
				}
				this.reload();
			});

			// 节点连线
			mySelf.myDiagram.addDiagramListener("LinkDrawn", async (e: any) => {
				const node = e.subject.part.data;
				this.nodeLink(node);
				// this.reload();
			});
		},

		/**
		 * 添加节点连线关系
		 */
		async nodeLink(node: any) {
			let from = this.dataList.find(item => item.id == node.from);
			let to = this.dataList.find(item => item.id == node.to);
			if (from) {
				from.next = (to && to.id) || "no";
				await this.modNode(from);
			}
			if (to) {
				to.previous = (from && from.id) || "no";
				await this.modNode(to);
			}
		},

		/**
		 * 移除节点连线关系
		 */
		async unNodeLink(node: any) {
			let from = this.dataList.find(item => item.id == node.from);
			let to = this.dataList.find(item => item.id == node.to);
			// console.log(`${from?.name} next null, ${to?.name} previous null`);
			if (from) {
				from.next = "no";
				await this.modNode(from);
			}
			if (to) {
				to.previous = "no";
				await this.modNode(to);
			}
		},
		async addNode(node: NodeInfo) {
			let pre = this.dataList.find(item => item.id == node.previous);
			let data: NodeInfo = {
				id: "",
				name: node.text,
				desc: "",
				loc: node.loc,
				previous: pre?.id || "no",
				next: "no",
				ruleId: this.ruleId,
				trigger: "dev_data",
				script: this.initEmptyScript()
			};
			let { result } = await addRuleProcessor(data);
			return result;
		},
		async modNode(node: NodeInfo) {
			node.loc = node.loc;
			return await modRuleProcessor(node);
		},
		async delNode(node: NodeInfo) {
			await delRuleProcessor(node);
			this.nodeParam = {
				id: "",
				name: "",
				next: "no",
				previous: "no",
				loc: "",
				schedule: "",
				trigger: "",
				ruleId: this.ruleId,
				script: this.initEmptyScript(),
				type: "default"
			};
		},
		reload() {
			this.$emit("reload");
		},
		initEmptyScript() {
			return `/*\n* 数据规则处理器\n* @sensor 数据对象\n* @context 元数据，如设备编号、分组等数据\n*/\nfunction f(sensor, context) {${this.exampleCode()}\n\t return sensor;\n}`;
		},
		exampleCode() {
			return `/**\n*\t example: sensor.z = sensor.x + sensor.y\n*/`;
		},
		getNodeOption() {
			return {
				movable: true, //是否可拖动
				deletable: true, //是否可删除
				selectable: true, //是否可选择
				selectionAdorned: true, //显示选中边框
				reshapable: false, // 重塑(改变shape形状边界时使用，将影响节点大小)
				resizable: false // 可调整大小的(手动调整节点大小时，节点内容显示区域时使用)
			};
		},
		// eslint-disable-next-line @typescript-eslint/no-unused-vars
		markerNode(param?: ShapeParam, type?: "input" | "calc" | "output") {
			let mySelf = this as any;
			return $(
				go.Panel, //表明需要创建一个panel面板对象//声明创建一个新的面板对象,自定义方式可参考mySelf.myDiagram.nodeTemplate
				"Auto", //页面布局为自动
				$(
					go.Shape, //声明构建一个圆形
					"RoundedRectangle",
					mySelf.markerNodeFull(param),
					new go.Binding("figure", "figure") //声明并创建一个新的图形
				),
				//节点文字
				mySelf.markerNodeText(param)
			);
		},
		markerNodeExt(margin: go.Margin, param?: ShapeParam) {
			return $(go.Shape, "Rectangle", { width: 5, height: param?.height || 60, margin: margin || 0, fill: "green" });
		},
		markerNodeText() {
			return $(
				go.TextBlock,
				{
					//声明一个可编辑文本域
					maxSize: new go.Size(360, NaN),
					wrap: go.TextBlock.WrapFit, //文本域换行
					editable: false //是否可编辑
				},
				new go.Binding("text").makeTwoWay()
			);
		},
		markerNodeFull(param: ShapeParam) {
			param = param || {};
			return {
				fill: param.color || "#e8e8e8", //内填充色
				cursor: "pointer", //指针
				stroke: param.border || "#ccc", //外框颜色
				strokeWidth: 3, //边框宽度
				portId: "",
				fromLinkable: true,
				fromLinkableSelfNode: false,
				fromLinkableDuplicates: true,
				toLinkable: true,
				toLinkableSelfNode: false,
				toLinkableDuplicates: true,
				width: param.width || 180,
				height: param.height || 58
			};
		},
		markerTooltip() {
			return {
				toolTip: $("ToolTip", $(go.TextBlock, { margin: 4 }, new go.Binding("text", "text")))
			};
		},
		initPalette() {
			let mySelf = this as any;
			let type = [
				{ category: "input", el: "input-palette", label: "收到数据", type: "input" },
				{ category: "calc", el: "calc-palette", label: "数据转换", type: "calc" },
				{ category: "output", el: "output-palette", label: "输出结果", type: "output" },
				{ category: "error", el: "error-palette", label: "输出错误", type: "output" }
			];
			type.forEach(item => {
				$(
					go.Palette,
					item.el || `${item.category}-palette`, // 必须命名或引用DIV.HTML元素
					{
						scrollsPageOnFocus: false,
						nodeTemplateMap: mySelf.myDiagram.nodeTemplateMap, // 共享myDiagram使用的模板
						model: new go.GraphLinksModel([
							{
								category: item.category,
								text: item.label
							}
						])
					}
				);
			});
		}
	}
};
</script>

<style lang="scss">
.gojs-page.full-panel {
	.splitter-pane {
		width: 100% !important;
	}
	.splitter-pane-resizer {
		display: none;
	}
}
.rule-detain-page {
	height: 100%;
	.diagram-canvas {
		border: 1px solid #cccccc;
	}
	.data-title {
		display: flex;
		align-items: center;
		padding-left: 10px;
	}
	.palette {
		height: 75px;
	}
	.form-info {
		box-sizing: border-box;
		width: 550px;
		.el-form {
			box-sizing: border-box;
			padding: 30px 15px 15px;
			border: 1px solid #cccccc;
		}
	}
}
</style>
